词条
通配符— ‘?’
有限制的通配符 —
- ? extends E
- ? super E
PECS — producer-extends, consumer-super
结论
输入参数是生产者或消费者时使用’?’
所有的compare和comparator都是consumer
声明方法时,用’?’取代仅出现一次的的类型参数
优势体现——论其必要性
我们先看之前讲 优先考虑泛型时的一个示例:
考虑为它增加一个方法:
这样的定义,在使用中会存在不灵活的问题:因为类型在定义之后就不可变了,如果想要将一个非E类型的Iterable push到Stack中是不可能的,这样的事实我们显然不能接受,那么该如何修改呢?答案就在本篇的主角——通配符’?’上
将上述API的参数修改为:
1 | Iterable<? extends E> src |
即可达到目的
说完? extends,再来看? super,同样考虑Stack的API,这一次是将Stack中的元素全部弹出到目标集合
理想状态下,所有的子类型的Stack都可以弹出到父类型的集合,但上图的API并不能实现这一目的;我们需要这样修改:
将参数类型修改为:
1 | Collection<? super E> dst |
这样一来,任意继承自父类型的Stack就都可以pop到父类型集合了,是不是很带感
有的同学可能会产生疑惑了,一会儿extends,一会儿super,好晕
还好有规律可总结:
PECS——producer-extends, consumer-super
即参数列表中 生产者总是使用? extends,而消费者则总是使用 ? super(不了解生产者、消费者设计模式的同学请自行翻书)
高级应用
原始API声明:
1 | <T extends Comparable<T>> T max1(List<T> list) |
修改后的API声明:
1 | <T extends Comparable<? super T>> T max2(List<? extends T> list) |
这一修改后的API,pecs都用上了,真有必要弄这么复杂了?
答案是肯定的
1 | List<ScheduleFuture<?>> futures = ...; |
这一futures不能调用max1方法,原因在于ScheduleFuture扩展的是Compare接口的Delayed接口的子接口,它可以与任意Delayed进行compare;而max2方法这样的声明就不会有这问题
E 与 ‘?’
1 | <E> void swap(List<E> list, int i, int j) |
你看上哪个了呢?
从灵活性的角度考虑,肯定是第二种好一些,但在类型参数不止一个时,就不能用’?’ 而要用类型参数了
使用第二种时需要注意:
只能把null放入List<?>中,这时为了保证灵活性,就需要写一个辅助捕捉类型的方法
1 | void swap(List<?> list, int i, int j){ |
get
这一篇看起来似乎有点绕,但我们真正在项目中写公共库API时,其实是需要用到’?’的,不是为了装,是真的能提升API的适用范围,减少工作量
不过任何收获都是要付出代价的,在写出高适用度的API时,一定记得写单元测试,进行高覆盖度的验证,保证准确性